package br.ufpe.cin.imlm2.jbeat.soot.path;

import java.util.Iterator;

import br.ufpe.cin.imlm2.jbeat.soot.common.CommonExceptions;

import soot.FastHierarchy;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;

/**
 * An immutable exception path that works like a linked list.
 * 
 * @author Irineu
 * 
 */
public class LinkedExceptionPath implements ExceptionPath {

	private final LinkedExceptionPath nextNode;
	private final SootClass exception;
	private final SootMethod node;
	
	// For caching
	private final SootMethod endPoint;
	private final int length;

	/**
	 * @param nextNode
	 *            Next path(node) in this path, null if this is the throw site
	 *            of an exception
	 * @param throwable
	 *            Exception being propagated
	 * @param node
	 *            Current node that rose or propagated the exception
	 */
	public LinkedExceptionPath(LinkedExceptionPath nextNode,
			SootClass throwable, SootMethod node) {
		if (throwable == null || node == null)
			throw new IllegalArgumentException(
					"Neither the exception type nor the node can be null.");
		this.verifyTyping(throwable);
		this.nextNode = nextNode;
		this.exception = throwable;
		this.node = node;
		
		if(nextNode != null) {
			this.endPoint = nextNode.getEndPoint();
			this.length = nextNode.length() + 1;			
		} else {
			this.endPoint = this.node;
			this.length = 1;
		}
	}

	// TODO Should we verify the SootClass type or our clients? 
	private void verifyTyping(SootClass throwable) {
		FastHierarchy fh = Scene.v().getOrMakeFastHierarchy();
		if(!fh.isSubclass(throwable, CommonExceptions.getTHROWABLE()))
			throw new IllegalArgumentException(throwable.getName() + " is not subclass of " +
					CommonExceptions.getTHROWABLE().getName());
	}

	@Override
	public SootMethod getStartingPoint() throws EmptyPathException {
		return this.node;
	}

	@Override
	public SootMethod getEndPoint() throws EmptyPathException {
		return this.endPoint;
	}

	@Override
	public boolean isEmpty() {
		// LinkedExceptionPath's can never be empty!
		return false;
	}

	@Override
	public int length() {
		return this.length;
	}

	@Override
	/**
	 * Returns an iterator for this path.
	 * Due to immutability of <code>LinkedExceptionPath</code> objects calls to the iterator
	 * <code>remove</code> method will throw an <code>UnsupportedOperationException</code>.
	 */
	public Iterator<SootMethod> iterator() {
		return new LinkedExceptionPathIterator();
	}

	@Override
	public SootClass getException() {
		return this.exception;
	}

	private class LinkedExceptionPathIterator implements Iterator<SootMethod> {

		private LinkedExceptionPath currentPath = LinkedExceptionPath.this;

		@Override
		public boolean hasNext() {
			return this.currentPath != null;
		}

		@Override
		public SootMethod next() {
			SootMethod node = this.currentPath.node;
			this.currentPath = this.currentPath.nextNode;
			return node;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException(
					"LinkedExceptionPath's are immutabled.");
		}

	}
		
}
